package cc.verywell.pureblock.msg.nfs.idx.extraDownload;

import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.DigestException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import com.sun.org.apache.bcel.internal.generic.NEW;

import cc.verywell.pureblock.msg.nfs.idx.extraUpload.UploadNeed;
import cc.verywell.pureblock.msg.nfs.idx.extraUpload.UploadSupply;
import cc.verywell.pureblock.msg.nfs.idx.extraUpload.UploadingMonitor;
import cc.verywell.pureblock.msg.nfs.idx.extraUpload.UploadingWatch;
import cc.verywell.pureblock.func.node.KeyShell;
import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.msg.nfs.idx.extraDownload.DloTargetCheck.TargetCheck;
import cc.verywell.pureblock.msg.nfs.idx.extraUpload.UploadManager.UPLOAD;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1encoder;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1.DataLink;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1.Sign;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1.Sub;
import cc.verywell.pureblock.net.AioProvider;
import cc.verywell.pureblock.util.StringUtil;
import cc.verywell.pureblock.util.sign.MD5;

/**
* 
* Copyright © 2020 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2020年1月31日 下午5:04:23
*/
public class DownloadManager {

	static public int SIZE_DATAREAD_DEFAULT = 102500;
	static public int SIZE_DATAREAD_MAX = 100*SIZE_DATAREAD_DEFAULT;
	
	DownloadNeed out;
	public DownloadManager(DownloadNeed out) {
		this.out= out;
	}

	private Map<DownloadingMonitor,KeyShell> downloadingMonitor = new ConcurrentHashMap<>();
	@Override
	public String toString() {
		return "DownloadManager monitor:"+downloadingMonitor.size();
	}
	public enum DOWMLOAD{
		OK,
		ERR,
		STRUCTUREERR,
		FILENOTEXIST,
		FILECANNOTLOCK,
		NODATA;
	}
	public static class DownloadCallback{
		final public DOWMLOAD backcode;
		final public DownloadingMonitor monitor;
		final public KeyShell mainKey;
		final public String targetPath;
		public DownloadCallback(DOWMLOAD backcode, DownloadingMonitor monitor, KeyShell mainKey, String targetPath) {
			this.backcode = backcode;
			this.monitor = monitor;
			this.mainKey = mainKey;
			this.targetPath = (targetPath!=null && !targetPath.isEmpty()) ? targetPath : null;
		}

	}
	///打开状态文件，继续下载;
	/**
	 * 1.检查3个参数
	 * 2.检查get/wait记录
	 * 3.在检查点导入get/wait记录
	 * 4.进行已下载文件的md5检查
	 * 5.继续下载
	 * @param logPath 为配置文件地址
	 * @return 仅OK成功，其它情况为不成功;
	 */
	public DownloadCallback download(Path logPath) {
		KeyShell mainKey=null;
		String targetPath=null;
		
		long totalFileSize = -1;
		HashMap<KeyShell,DloMapWait> mapTemp = new HashMap<KeyShell,DloMapWait>();
		PositionAl[] extraAls = new PositionAl[3];
		HashMap<Long,TargetCheck> blocksCheckWait = new HashMap<>();
		for (int i  = 0;i<extraAls.length;i++) {
			extraAls[i] = new PositionAl();
		}
        long lastPosition = 0;
       try (
        		//FileInputStream input = new FileInputStream(logPath.toFile());
        		//FileChannel channel = input.getChannel();
				RandomAccessFile fileaccess = new RandomAccessFile(logPath.toFile(), "rw");
				FileChannel channel = fileaccess.getChannel();
				FileLock filelock = channel.tryLock();
              ) {
        	if(filelock==null)return new DownloadCallback(DOWMLOAD.FILECANNOTLOCK,null,null,null);
         	//在检查点处理STATE_DATA
         	try{
            	{
                	ByteBuffer intlength = ByteBuffer.allocate(4);
                	int length;
                	//取得KEY
                	length = getSize(intlength,channel);
                	byte[] keybb = new byte[length];
                	if(channel.read(ByteBuffer.wrap(keybb))!=keybb.length)throw new EOFException();
                	mainKey = new KeyShell(keybb);
                	//取得文件路径
                	{
                		length = getSize(intlength,channel);
                		byte[] bs = new byte[length];
                		if(channel.read(ByteBuffer.wrap(bs))!=bs.length)throw new EOFException();
                		targetPath = StringUtil.byteToString(bs);
                	}
            	}
            	lastPosition = channel.position();
            	
            	///初始信息获取
            	ByteBuffer headbuffer = ByteBuffer.allocate(9);
            	{
            		int read = channel.read(headbuffer);
            		if(read!=headbuffer.capacity())throw new EOFException();
            		headbuffer.flip();
            		if(headbuffer.get()!=DloLog.STATE_INIT_POINT)throw new IOException("illegal init");
            		totalFileSize = headbuffer.getLong();
            		if(totalFileSize<0)throw new IOException("illegal filesize");
            		
            		//为已初始化准备好等待列表;
            		DloMapWait w = new DloMapWait();
        			if(totalFileSize>0)
        				w.structure_list  = new DltoolIterator<>(new DltoolPosLen(0, totalFileSize));
        			mapTemp = new HashMap<KeyShell,DloMapWait>();
                	mapTemp.put(mainKey, w);
            	}
            	lastPosition = channel.position();
            	
            	///必要信息获取完毕
           		ArrayList<Download_CHECK> dataAltemp = new ArrayList<>();
            	ByteBuffer databuffer = ByteBuffer.allocate(DloLog.SIZE_DATAREAD_DEFAULT);
            	for(;;) {
            		headbuffer.clear();
            		int read = channel.read(headbuffer);
            		if(read<0)break; //正常退出
            		if(read!=headbuffer.capacity())throw new EOFException();
            		headbuffer.flip();
            		byte state = headbuffer.get();
            		long length = headbuffer.getLong();
            		long pos = channel.position();
            		switch(state) {
            			/********* STATE_CHECK_POINT 结构  **********
            			 * 	1byte_STATE_CHECK_POINT 8byte当前position (??以后要加上MD5效验??)
            			 */
            			case DloLog.STATE_CHECK_POINT:{//到了检查点可以更新lastPosition
            				if(length!=pos)throw new EOFException();
            				{//在检查点这里开始处理STATE_DATA
            					for (Download_CHECK data : dataAltemp) {
            						KeyShell key = data.key;
            						
            						if(data.isGet) {
                						//锁定一个有用的W
                						DloMapWait w = mapTemp.remove(key);
                						if(w==null) {
                							if(Pblog.DEBUG)Pblog.DEBUG("reload get not need bag! ",key.key);
                							continue; //没有w就证明这里不需要;
                						}else {
                							w.isRemoved=true;
                						}
                						if(data.hash!=null) {
                							DltoolIterator<DltoolPosLen> p = data.poslen;
                							while(p!=null) {
                								//排序+检查
                								TargetCheck c = new TargetCheck(data.key,data.hash,p.t,false);
                								TargetCheck old = blocksCheckWait.putIfAbsent(c.pos.pos, c);
                								if(old!=null && old!=c) {
                									if(!old.key.equals(c.key) || !old.pos.equals(c.pos) /*||!Arrays.equals(old.hash, c.hash)*/) {
                										//throw new IOException("err! more than 1 date write same place");
                										return new DownloadCallback(DOWMLOAD.STRUCTUREERR,null,mainKey,targetPath);
                									}
                								}
                								//end
                								p = p.prv;
                							}
                						}
            						}else {
            							DloMapWait dw = mapTemp.compute(key, (k,v)->{
            								if(v==null)return new DloMapWait();
            								return v;
            							});
            							DltoolIterator<DltoolPosLen> newPos = dw.structure_list;
            							DltoolIterator<DltoolPosLen> p = data.poslen;
            							while(p!=null) {
            								newPos = DltoolIterator.toNew(newPos,p.t);
            								p = p.prv;
            							}
            							dw.structure_list = newPos;
            						}
            						
								}
            					dataAltemp.clear();
            				}
            				lastPosition = pos;
            				for (PositionAl positionAl : extraAls)
            					positionAl.confirm();
            				break;
            			}
            			case DloLog.STATE_DATA_GET:
            			case DloLog.STATE_DATA_WAIT:{
            				if(databuffer.capacity()<length)databuffer=DloLog.ensureCapacity(databuffer, length);
            				else databuffer.clear();
            				databuffer.limit((int)length);
            				if(channel.read(databuffer)!=length)throw new EOFException();
            				
            				boolean isGet = state==DloLog.STATE_DATA_GET;
            				databuffer.flip();
            				byte[] bs = new byte[databuffer.getShort()];
            				databuffer.get(bs);
            				KeyShell k = new KeyShell(bs);
            				if(isGet && databuffer.hasRemaining()) {
            					bs = new byte[databuffer.getShort()];
            					databuffer.get(bs);
            				}else {
            					bs = null;
            				}
            				
            				Download_CHECK check = new Download_CHECK(isGet, k, bs);
            				while(databuffer.hasRemaining()) {
            					check.add(new DltoolPosLen(databuffer.getLong(), databuffer.getLong()));
            				}
            				dataAltemp.add(check);
            				
            				break;
            			}
            			/********** data——block 结构  **********
            			 * 1byte类型 8byte结构length []余下为内容 结构与structure相同
            			 */
            			case DloLog.STATE_ALIAS:
            			case DloLog.STATE_SIGN:
            			case DloLog.STATE_EXTRA:{
            				extraAls[state].add(pos-8);//再次读取时能马上知道边界
            				channel.position(pos+length);
            				break;
            			}
            			default:{
            				throw new IOException("unknow STATE : "+state+" on "+(pos-9)+" at "+logPath);
            			}
        			}
            	}
    		} catch (EOFException e) {
            } catch (IOException e) {
    			e.printStackTrace();
        	} finally {
    			if(lastPosition!=0 && lastPosition!=channel.size()) {
          		   if(Pblog.FS)Pblog.FS(logPath," resize ",channel.size()," -> ",lastPosition);
          		   channel.truncate(lastPosition);
    			}
			}
       } catch (FileNotFoundException e) {
       		return new DownloadCallback(DOWMLOAD.FILENOTEXIST,null,mainKey,targetPath);
       } catch (IOException e) {
			e.printStackTrace();
			return new DownloadCallback(DOWMLOAD.ERR,null,mainKey,targetPath);
		}
        /////此时channel已关闭;
        if(mainKey==null || targetPath==null)return new DownloadCallback(DOWMLOAD.NODATA,null,mainKey,targetPath);
        if(targetPath.isEmpty())targetPath = null;
        ArrayList<TargetCheck> blocksCheckWaitAl = new ArrayList<TargetCheck>(blocksCheckWait.values());
		{//碎片检查
			///blocksCheckWait 文件顺序排列;
	        blocksCheckWait.clear();
	        blocksCheckWait = null;
			Collections.sort(blocksCheckWaitAl,(a,b)->{
				return a.pos.compareTo(b.pos);
			});

			ArrayList<DltoolPosLen> dlwal = new ArrayList<>();
			for (DloMapWait dlwait : mapTemp.values()) {
				DltoolIterator<DltoolPosLen> dlwit = dlwait.structure_list;
				while(dlwit!=null) {
					if(dlwit.t!=null && dlwit.t.len!=0) {
						dlwal.add(dlwit.t);
					}
					dlwit = dlwit.prv;
				}
				
			}
			Collections.sort(dlwal);
			int dlwalcount = 0;
			long lastPos = 0;
			if(blocksCheckWaitAl.isEmpty()) {
				while(dlwalcount<dlwal.size() && dlwal.get(dlwalcount).pos==lastPos) {
					lastPos+=dlwal.get(dlwalcount++).len;
				}
				if(dlwalcount!=dlwal.size()) {
					if(Pblog.FS)Pblog.FS("log ",logPath," file err at pos:",lastPos,"!=",dlwal.get(dlwalcount++).pos);
					return new DownloadCallback(DOWMLOAD.STRUCTUREERR,null,mainKey,targetPath);
				}
			}else {
				for (TargetCheck t : blocksCheckWaitAl) {
					if(t.pos.pos!=lastPos) {
						while(dlwalcount<dlwal.size() && dlwal.get(dlwalcount).pos==lastPos) {
							lastPos+=dlwal.get(dlwalcount++).len;
						}
					}
					if(t.pos.pos!=lastPos) {
						if(Pblog.FS)Pblog.FS("log ",logPath," file err at pos:",lastPos,"!=",t.pos.pos);
						return new DownloadCallback(DOWMLOAD.STRUCTUREERR,null,mainKey,targetPath);
					}else {
						lastPos += t.pos.len;
					}
				}
			}
			if(lastPos!=totalFileSize) {
				if(Pblog.FS)Pblog.FS("log ",logPath," file err at totalFileSize:",lastPos,"!=",totalFileSize);
				return new DownloadCallback(DOWMLOAD.STRUCTUREERR,null,mainKey,targetPath);
			}
		}

        
		boolean targetExists = false;
		boolean logExists = true;
		DloTarget target = null;
		DloLog log = null;
		DloMap map = null;
		DownloadingMonitor m = null;
		boolean startOK = false;
		DloTargetCheck check = null;
		try {
			if(null!=targetPath)
				targetExists = Files.exists(Paths.get(targetPath));
			target = new DloTarget(targetPath);
			//logExists = Files.exists(logPath);
			log = new DloLog(logPath.toFile(),/*false*/true);
			log.posAlias = extraAls[DloLog.STATE_ALIAS].getAl();
			log.posSign = extraAls[DloLog.STATE_SIGN].getAl();
			log.posExtra = extraAls[DloLog.STATE_EXTRA].getAl();
			map = new DloMap(mainKey, log, target, mapTemp, totalFileSize>=0, totalFileSize);
			//初始化完成
			m = new DownloadingMonitor(targetPath,logPath,log,target,map,this);
			downloadingMonitor.put(m, mainKey);
			if(totalFileSize>=0) {//已初始化，
	        	//检查目标文件, 修正文件大小;
	        	target.resetSize(totalFileSize);
        		if(!blocksCheckWaitAl.isEmpty()) {
        			check = target.useCheck(map);
        			for (TargetCheck t : blocksCheckWaitAl) {
        				check.join(t);
					}
        		}
			}
			///目标文件MD5检查完毕，开始运作;
			
			////log.logWrite_initpath(mainKey, targetPath);
			startOK = true;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (DigestException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			if(!startOK) {
				if(target!=null) {
					try{target.close();}catch(IOException e){};
					if(!targetExists && Files.exists(Paths.get(targetPath)))
						try {Files.delete(Paths.get(targetPath));} catch (IOException e) {}
				}
				if(log!=null) {
					try{log.close();}catch(IOException e){};
					if(!logExists && Files.exists(logPath))
						try {Files.delete(logPath);} catch (IOException e) {}
				}
				if(m!=null) {
					m.close();
				}
			}
		}
		if(!startOK)return new DownloadCallback(DOWMLOAD.ERR,null,mainKey,targetPath);
		
		//TODO 最好在异步处理，否则无法立即返回;
		if(!mapTemp.isEmpty()) {
			if(check!=null)check.waitFinish();
			m.wantList(mapTemp.keySet().toArray(new KeyShell[mapTemp.size()]));
		}else if(totalFileSize>=0){ //已初始化,证明下载已完成
			if(check!=null)check.waitFinish();
			try {
				map.checkFinish(null);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}else { //未初始化
			m.wantList(mainKey);
		}
		
		return new DownloadCallback(DOWMLOAD.OK,m,mainKey,targetPath);
        
	}
	static private int getSize(ByteBuffer bb,FileChannel channel) throws IOException {
		bb.clear();
		if(channel.read(bb)!=bb.capacity())throw new EOFException();
		bb.flip();
		return bb.getInt();
	}
//	static private long getSizeLong(ByteBuffer bb,FileChannel channel) throws IOException {
//		bb.clear();
//		if(channel.read(bb)!=bb.capacity())throw new EOFException();
//		bb.flip();
//		return bb.getLong();
//	}
	static private class PositionAl{
		private ArrayList<Long> al = new ArrayList<Long>();
		private int confirmSize = 0;
		public void add(long l) {al.add(l);}
		public void confirm() {confirmSize = al.size();}
		public ArrayList<Long> getAl(){
			while(al.size()>confirmSize)al.remove(al.size()-1);
			return al;
		}
	}
	static public class Download_CHECK {
		public final KeyShell key;
		public final byte[] hash;
		public final boolean isGet;
		public Download_CHECK(boolean isGet,KeyShell key, byte[] hash) {
			this.isGet = isGet;
			this.key = key;
			this.hash = hash;
		}
		public DltoolIterator<DltoolPosLen> poslen = null;
		public void add(DltoolPosLen pl) {
			poslen = DltoolIterator.toNew(poslen, pl);
		}
	}

	

	
	
	
	
	
	///创建新的状态文件，开始一个新流程
	/**
	 * 开始一个新的下载，通常只会截获一个KEY，有时候会有一个目标地址，
	 * @param dataPath
	 * @param logPath
	 * @param extraPath
	 * @return
	 */
	//TODO
	public DownloadCallback download(final KeyShell mainKey, Path logPath, String targetPath) {
		Objects.requireNonNull(mainKey);
		Objects.requireNonNull(logPath);
		boolean targetExists = false;
		boolean logExists = false;
		DloTarget target = null;
		DloLog log = null;
		DloMap map = null;
		boolean startOK = false;
		try {
			try {
				Files.createDirectories(logPath.getParent());
			} catch (Exception e) {
				e.printStackTrace();
				return new DownloadCallback(DOWMLOAD.FILENOTEXIST,null,mainKey,targetPath);
			}
			if(null!=targetPath)
				targetExists = Files.exists(Paths.get(targetPath));
			target = new DloTarget(targetPath);
			logExists = Files.exists(logPath);
			log = new DloLog(logPath.toFile(),false);
			map = new DloMap(mainKey, log, target);
			//初始化完成
			log.logWrite_initpath(mainKey, targetPath);
			startOK = true;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(!startOK) {
				if(target!=null) {
					try{target.close();}catch(IOException e){};
					if(!targetExists && Files.exists(Paths.get(targetPath)))
						try {Files.delete(Paths.get(targetPath));} catch (IOException e) {}
				}
				if(log!=null) {
					try{log.close();}catch(IOException e){};
					if(!logExists && Files.exists(logPath))
						try {Files.delete(logPath);} catch (IOException e) {}
				}
			}
		}
		if(!startOK)return new DownloadCallback(DOWMLOAD.ERR,null,mainKey,targetPath);
		DownloadingMonitor m = new DownloadingMonitor(targetPath,logPath,log,target,map,this);
		downloadingMonitor.put(m, mainKey);
		
		//TODO 最好在异步处理，否则无法立即返回;
		m.wantList(mainKey);
		
		return new DownloadCallback(DOWMLOAD.OK,m,mainKey,targetPath);
	}
	
	
	void removeMonitorbyDownloadingMonitor(DownloadingMonitor m) {
		if(null!=downloadingMonitor.remove(m)) {
			//TODO 告知监听器
		}
	}
	void fireFinishbyDownloadingMonitor(DownloadingMonitor m) {
		Pblog.DEBUG("fireFinishbyDownloadingMonitor ",m);
		//TODO uploadingMonitor正在做种，是否要立即关闭?
		//m.close();
	}

	/**
	 * 作DownloadNeed的桥梁
	 */
	
	
}
